package service

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"quant/backend/api/dto"
	"quant/backend/constants"
	"quant/backend/db"
	"quant/backend/entity"
	"quant/backend/evaluator"
	"quant/backend/manager"
	"quant/backend/qmt"
	"sort"
	"strings"
	"time"

	"github.com/duke-git/lancet/v2/datetime"
	"github.com/duke-git/lancet/v2/maputil"
	"github.com/duke-git/lancet/v2/mathutil"
	"github.com/duke-git/lancet/v2/slice"
	"github.com/sirupsen/logrus"
)

type MarketService struct {
	dailyManager *manager.DailyManager
	stockManager *manager.StockManager
}

func NewMarketService() *MarketService {
	return &MarketService{
		dailyManager: manager.NewDailyManager(),
		stockManager: manager.NewStockManager(),
	}
}
func (ms *MarketService) getLastDay() (string, error) {
	dates, err := qmt.GetTradingDates(time.Now().AddDate(0, -1, 0).Format(constants.TimeLayout), time.Now().AddDate(0, 0, -1).Format(constants.TimeLayout))
	if err != nil {
		return "", err
	}
	sort.SliceStable(dates, func(i, j int) bool { return dates[i] > dates[j] })
	return dates[0], nil
}
func (ms *MarketService) GetMonitoredList() ([]entity.PreferredEntity, error) {
	var err error
	var positions []qmt.PositionModel
	var codeList []string

	var pList []entity.PreferredEntity
	err = db.GetMySQL().Where("time = ?", time.Now().Format("20060102")).Find(&pList)
	if err != nil {
		return nil, err
	}
	logrus.Info("pList.count: ", len(pList))
	// t.Log(fmt.Sprintf("股票代码:%s,持仓数量:%d,可用数量:%d,持仓价格:%f,持仓市值:%f", dd.StockCode, dd.Volume, dd.CanUseVolume, dd.Price, dd.MarketValue))
	if positions, err = qmt.QueryStockPositions(); err != nil {
		return nil, err
	}
	if len(positions) > 0 {
		// 把持仓的股票的加入codeList
		for _, dd := range positions {
			codeList = append(codeList, dd.StockCode)
		}
		// 获取上一个交易日
		lastDay, err := ms.getLastDay()
		if err != nil {
			return nil, err
		}
		// 根据持仓的股票代码，查询上一个交易日的数据
		var des []entity.DailyEntity
		err = db.GetMySQL().Where("stock_code in (?)", strings.Join(codeList, ",")).Where("time = ?", lastDay).Find(&des)
		if err != nil {
			return nil, err
		}
		logrus.Info("des.count: ", len(des))

		for _, de := range des {
			var pe entity.PreferredEntity
			pe.StockCode = de.StockCode
			pe.StockName = de.StockName
			pe.Time = de.Time
			pe.LastClose = de.Close
			pe.LastPrice = de.Close
			pe.Rate = (pe.LastPrice - pe.LastClose) / pe.LastClose * 100
			pe.Score = 0
			pList = append(pList, pe)
		}
	}
	sort.SliceStable(pList, func(i, j int) bool {
		return pList[i].Score > pList[j].Score
	})
	return pList, nil
}

// Subscribe 开始订阅
func (ms *MarketService) Subscribe() (string, error) {
	ginService := NewGinService()
	_, err := ginService.Start()
	if err != nil {
		return "", err
	}
	subscribeService := NewSubscribeService()
	_, err = subscribeService.Subscribe()
	if err != nil {
		return "", err
	}
	return "订阅成功", nil
}

// Unsubscribe 取消订阅
func (ms *MarketService) Unsubscribe() (string, error) {
	subscribeService := NewSubscribeService()
	_, err := subscribeService.Unsubscribe()
	if err != nil {
		return "", err
	}
	ginService := NewGinService()
	_, err = ginService.Stop()
	if err != nil {
		return "", err
	}

	return "停止订阅", nil
}

// DownloadMarket 获取日行情数据
// 获取从 startDate 开始至 endDate 的日行情数据
func (ms *MarketService) DownloadMarket(startDate, endDate string) (string, error) {
	// logrus.Info("开始获取日行情数据...")
	constants.App.Event.Emit("logs", "开始获取日行情数据\n")
	dateList, err := qmt.GetTradingDates(startDate, endDate) // 获取从 startDate 开始到当前日的交易日列表
	if err != nil {
		constants.App.Event.Emit("logs", err.Error())
		return "", err
	}
	if len(dateList) <= 0 {
		constants.App.Event.Emit("logs", "未获取到任何交易日，跳过后续处理")
		return "", err
	}

	sort.Strings(dateList) // 排序交易日列表，升序
	allStartTime := time.Now()
	// 2. 获取沪深A股的所有股票代码列表
	stockEntities, err := ms.stockManager.FindAll()
	if err != nil {
		return "", err
	}
	mapStock := slice.KeyBy(stockEntities, func(item entity.StockEntity) string {
		return item.Code
	})
	codeList := maputil.Keys(mapStock)
	for _, d := range dateList {
		// 如果当前交易日是今天，则跳过
		// if strings.EqualFold(d, time.Now().Format(constants.TimeLayout)) {
		// 	continue
		// }
		startTime := time.Now() // 获取开始时间，用于统计耗时
		constants.App.Event.Emit("logs", fmt.Sprintf("[%s]开始获取...", d))
		// constants.App.Event.Emit("logs", fmt.Sprintf("[%s][%s]开始获取...", time.Now().Format("15:04:05"), d))

		// 删除该交易日的日行情数据
		err := ms.dailyManager.Delete(&entity.DailyEntity{Time: d})
		if err != nil {
			constants.App.Event.Emit("logs", err.Error())
			return "", err
		}

		// 获取日期为 d 的所有股票日行情数据
		m := make(map[string]string)
		if m, err = qmt.GetDailyMarketData(d, strings.Join(codeList, ",")); err != nil {
			constants.App.Event.Emit("logs", fmt.Sprintf("失败: %v\n", err))
			return "", err
		}

		// 把日行情数据转换成实体对象
		var entities = make([]entity.DailyEntity, 0)
		if entities, err = ms.createDailyEntities(m); err != nil {
			constants.App.Event.Emit("logs", fmt.Sprintf("失败: %v\n", err))
			return "", err
		}
		logrus.Info(fmt.Sprintf("[%s]entities.count: %d", d, len(m)))

		mm, err := ms.getHisDailyEntitiesForMap(d)
		if err != nil {
			return "", err
		}
		affected, err := ms.dailyManager.WithTx(context.Background(), func(svc *manager.BaseManager[entity.DailyEntity]) (int64, error) {
			var count int64
			for _, dailyEntity := range entities {
				list := mm[dailyEntity.StockCode]
				sort.SliceStable(list, func(i, j int) bool {
					return list[i].Time > list[j].Time
				})
				// // 更新日行情对象
				// if er := ms.updateDailyDetail(&dailyEntity); err != nil {
				// 	return 0, fmt.Errorf("DownloadMarket: updateDailyDetail failed: %w", er)
				// }
				stockEntity := mapStock[dailyEntity.StockCode]
				err := ms.updateDailyDetail2(&dailyEntity, &stockEntity)
				if err != nil {
					return 0, err
				}
				num, er := svc.Create(&dailyEntity)
				// 保存至数据库
				if err != nil {
					return 0, fmt.Errorf("DownloadMarket -> xorm.Insert 操作失败：%w", er)
				}
				count += num
			}
			return count, nil
		})

		endTime := time.Now()
		constants.App.Event.Emit("logs", fmt.Sprintf("-->成功!,记录数: %d, 耗时(秒):%d\n", affected, datetime.BetweenSeconds(startTime, endTime)))
		time.Sleep(10 * time.Millisecond)
	}
	allEndTime := time.Now()
	// logrus.WithField("总耗时(秒)", datetime.BetweenSeconds(allStartTime, allEndTime)).
	// 	Info("所有行情数据获取完成")

	constants.App.Event.Emit("logs", fmt.Sprintf("所有行情数据获取完成,总耗时(秒):%d\n", datetime.BetweenSeconds(allStartTime, allEndTime)))
	return "所有行情数据获取完成", nil

}

// 获取历史日行情数据转换成实体对象Map，key 为 股票代码，value 为该股票的日行情数据列表
func (ms *MarketService) getHisDailyEntitiesForMap(d string) (map[string][]entity.DailyEntity, error) {
	var dailyEntities = make([]entity.DailyEntity, 0)
	engine := db.GetMySQL()
	err := engine.Where("time < ?", d).Find(&dailyEntities)
	if err != nil {
		return nil, err
	}
	var dailyEntityMap = make(map[string][]entity.DailyEntity)
	for _, dailyEntity := range dailyEntities {
		dailyEntityMap[dailyEntity.StockCode] = append(dailyEntityMap[dailyEntity.StockCode], dailyEntity)
	}
	return dailyEntityMap, nil
}

func (ms *MarketService) createDailyEntities(m map[string]string) ([]entity.DailyEntity, error) {
	var entities = make([]entity.DailyEntity, 0)
	if m == nil || len(m) == 0 {
		return entities, errors.New("没有数据")
	}

	for code, data := range m {
		var dailyDto dto.DailyDto
		if err := json.Unmarshal([]byte(data), &dailyDto); err != nil {
			return entities, fmt.Errorf("createDailyEntities: json.Unmarshal error for code %s: %w", code, err)
		}

		if dailyDto.Time == nil {
			continue // 或者根据业务需求决定是否报错
		}

		for k := range dailyDto.Time {
			if dailyDto.SuspendFlag[k] == 1 {
				continue
			}
			de := entity.DailyEntity{
				StockCode: code,
				Time:      k,
				Open:      mathutil.RoundToFloat(dailyDto.Open[k], constants.Precision2),
				High:      mathutil.RoundToFloat(dailyDto.High[k], constants.Precision2),
				Low:       mathutil.RoundToFloat(dailyDto.Low[k], constants.Precision2),
				Close:     mathutil.RoundToFloat(dailyDto.Close[k], constants.Precision2),
				Volume:    mathutil.RoundToFloat(dailyDto.Volume[k], constants.Precision2),
				Amount:    mathutil.RoundToFloat(dailyDto.Amount[k], constants.Precision2),
				PreClose:  mathutil.RoundToFloat(dailyDto.PreClose[k], constants.Precision2),
			}

			entities = append(entities, de)
		}
	}
	return entities, nil
}

// 更新日行情对象的每个属性内容
func (ms *MarketService) updateDailyDetail2(dailyEntity *entity.DailyEntity, stock *entity.StockEntity) error {

	dailyEntity.StockName = stock.Name
	dailyEntity.FloatVolume = mathutil.RoundToFloat(stock.FloatVolume, constants.Precision2)
	dailyEntity.TotalVolume = mathutil.RoundToFloat(stock.TotalVolume, constants.Precision2)
	dailyEntity.FloatAmount = mathutil.RoundToFloat(stock.FloatVolume*dailyEntity.PreClose, constants.Precision2)
	dailyEntity.TotalAmount = mathutil.RoundToFloat(stock.TotalVolume*dailyEntity.PreClose, constants.Precision2)

	if dailyEntity.FloatAmount > 0 && dailyEntity.TotalAmount > 0 {
		dailyEntity.TurnoutRate = mathutil.RoundToFloat((dailyEntity.Volume*100/dailyEntity.FloatVolume)*100, constants.Precision2)
	} else {
		dailyEntity.TurnoutRate = 0
	}
	return nil
}

// 更新日行情对象的每个属性内容
func (ms *MarketService) updateDailyDetail(dailyEntity *entity.DailyEntity) error {
	var err error
	var instrumentDto dto.InstrumentDto

	if instrumentDto, err = qmt.GetInstrumentDetail(dailyEntity.StockCode); err != nil {
		return fmt.Errorf("updateDailyDetail([DailyModel]) -> json.Unmarshal 出现错误：%w", err)
	}

	dailyEntity.StockName = instrumentDto.InstrumentName
	// dailyEntity.DownStopPrice = mathutil.RoundToFloat(instrumentDto.DownStopPrice, constants.Precision2)
	// dailyEntity.UpStopPrice = mathutil.RoundToFloat(instrumentDto.UpStopPrice, constants.Precision2)
	dailyEntity.FloatVolume = mathutil.RoundToFloat(instrumentDto.FloatVolume, constants.Precision2)
	dailyEntity.TotalVolume = mathutil.RoundToFloat(instrumentDto.TotalVolume, constants.Precision2)
	dailyEntity.FloatAmount = mathutil.RoundToFloat(instrumentDto.FloatVolume*dailyEntity.PreClose, constants.Precision2)
	dailyEntity.TotalAmount = mathutil.RoundToFloat(instrumentDto.TotalVolume*dailyEntity.PreClose, constants.Precision2)

	if dailyEntity.FloatAmount > 0 && dailyEntity.TotalAmount > 0 {
		dailyEntity.TurnoutRate = mathutil.RoundToFloat((dailyEntity.Volume*100/dailyEntity.FloatVolume)*100, constants.Precision2)
	} else {
		dailyEntity.TurnoutRate = 0
	}
	return nil
}

// CalculateMAn 计算日行情对象的 MA 值
func (ms *MarketService) CalculateMAn(startDate, endDate string) error {
	constants.App.Event.Emit("logs", "开始计算 MAn \n")
	constants.App.Event.Emit("logs", "获取日历... ")
	dateList, err := qmt.GetTradingDates(startDate, endDate) // 获取从 startDate 开始到当前日的交易日列表
	if err != nil {
		constants.App.Event.Emit("logs", fmt.Sprintf("失败！%s\n", err.Error()))
		return err
	}
	constants.App.Event.Emit("logs", "完成！\n")

	sort.Strings(dateList) // 排序交易日列表，升序
	time.Sleep(time.Millisecond * 10)
	err = ms.dailyManager.UpdateMAn(startDate, endDate)
	if err != nil {
		constants.App.Event.Emit("logs", fmt.Sprintf("失败！>> %s\n", err.Error()))
	}
	return err
}

// CalculateMACD 计算日行情对象的 MACD 值(N,M,K) (默认 N=12,M=26,K=9),
func (ms *MarketService) CalculateMACD(startDate, endDate string) error {
	return ms.dailyManager.CalculateMACD(startDate, endDate)
}

// CalculateKDJ 计算日行情对象的 KDJ 值
func (ms *MarketService) CalculateKDJ(startDate, endDate string) error {
	// length := len(entities)
	// // RSV = (C−L) ÷ (H−L) × 100  C为当天的收盘价；Ln为之前n日内的最低价；Hn为之前n日内的最高价。
	// // de.Rsv = mathutil.RoundToFloat((de.Close-ms.minLow(entities, 9))/(ms.maxHigh(entities, 9)-ms.minLow(entities, 9))*100, constants.Precision_6)
	// // K值=2/3×第8日K值+1/3×第9日RSV
	// // D值=2/3×第8日D值+1/3×第9日K值
	// // J值=3×第9日K值-2×第9日D值
	//
	// // 公式中，C为第9日的收盘价；L9为9日内的最低价；H9为9日内的最高价。
	// // K值=2/3×第8日K值＋1/3×第9日RSV
	// // D值=2/3×第8日D值＋1/3×第9日K值
	// // J值=3*第9日K值-2*第9日D值
	// // 若无前一日K值与D值，则可以分别用50代替。
	// // 第一天
	// if length == 0 {
	// 	if de.High == de.Low {
	// 		de.Rsv = 0
	// 	} else {
	// 		de.Rsv = mathutil.RoundToFloat((de.Close-de.Low)/(de.High-de.Low)*100, constants.Precision6)
	// 	}
	// 	de.K = 50.0
	// 	de.D = 50.0
	// 	de.J = mathutil.RoundToFloat(3*de.K-2*de.D, constants.Precision6)
	// 	// } else if length == 9 {
	// 	// 	de.Rsv = mathutil.RoundToFloat((de.Close-(ms.minLow(entities)))/(ms.maxHigh(entities)-ms.minLow(entities))*100, constants.Precision_6)
	// 	// 	de.K = mathutil.RoundToFloat(50.0*2/3+de.Rsv/3, constants.Precision_6) // K值=2/3×第8日K值＋1/3×第9日RSV
	// 	// 	de.D = mathutil.RoundToFloat(50.0*2/3+de.K/3, constants.Precision_6)
	// 	// 	de.J = mathutil.RoundToFloat(3*de.K-2*de.D, constants.Precision_6)
	// } else {
	// 	var list []entity.DailyEntity
	// 	if length <= 9 {
	// 		list = entities
	// 	} else {
	// 		list = entities[0:9]
	// 	}
	// 	if de.High == de.Low || ms.maxHigh(list)-ms.minLow(list) == 0 {
	// 		de.Rsv = 0
	// 	} else {
	// 		de.Rsv = mathutil.RoundToFloat((de.Close-(ms.minLow(list)))/(ms.maxHigh(list)-ms.minLow(list))*100, constants.Precision6)
	// 	}
	// 	// de.Rsv = mathutil.RoundToFloat((de.Close-(ms.minLow(list)))/(ms.maxHigh(list)-ms.minLow(list))*100, 5)
	// 	de.K = mathutil.RoundToFloat(list[0].K*2/3+de.Rsv/3, constants.Precision6) // K值=2/3×第8日K值＋1/3×第9日RSV
	// 	de.D = mathutil.RoundToFloat(list[0].D*2/3+de.K/3, constants.Precision6)
	// 	de.J = mathutil.RoundToFloat(3*de.K-2*de.D, constants.Precision6)
	// }

	return nil

}

func (ms *MarketService) maxHigh(entities []entity.DailyEntity) (h float64) {
	h = entities[0].High
	for i := 0; i < len(entities); i++ {
		if entities[i].High > h {
			h = entities[i].High
		}
	}
	return
}
func (ms *MarketService) minLow(entities []entity.DailyEntity) (l float64) {
	l = entities[0].Low
	for i := 0; i < len(entities); i++ {
		if entities[i].Low < l && entities[i].Low > 0.01 {
			l = entities[i].Low
		}
	}
	return
}

// CalculatePreferred 生成推荐股票列表
func (ms *MarketService) CalculatePreferred(startDate, endDate string) error {
	constants.App.Event.Emit("logs", "开始评估股票")
	codeList, err := qmt.GetStockCodeListBySector(constants.SectorName)
	if err != nil {
		constants.App.Event.Emit("logs", fmt.Sprintf("GeneratePromisingList()->GetStockCodeListBySector: %v", err))
		return fmt.Errorf("GeneratePromisingList()->GetStockCodeListBySector: %w", err)
	}

	if len(codeList) == 0 {
		logrus.Warn("获取到的股票代码列表为空")
		constants.App.Event.Emit("logs", fmt.Sprintf("获取到的股票代码列表为空"))
		return errors.New("获取到的股票代码列表为空")
	}
	configMap, err := manager.NewConfigManager().GetMap()
	if err != nil {
		return err
	}

	dateList, _ := qmt.GetTradingDates(startDate, endDate) // 获取从 startDate 开始到当前日的交易日列表
	sort.Strings(dateList)                                 // 排序交易日列表，升序
	engine := db.GetMySQL()

	for _, d := range dateList {
		// 清除日期 对应的推荐股票列表
		_, err := engine.Where("time = ?", d).Delete(&entity.PreferredEntity{})
		if err != nil {
			return err
		}

		dailyEntityMap, err := ms.getHisDailyEntitiesForMap(d)
		count := 0

		for _, code := range codeList {
			entities := dailyEntityMap[code]
			sort.SliceStable(entities, func(i, j int) bool {
				return entities[i].Time > entities[j].Time
			})
			if len(entities) < 2 {
				continue
			}
			isOk, err := ms.evaluate(entities, configMap)

			if err != nil {
				return err
			}

			if !isOk {
				continue
			}

			dailyEntity := entities[0]
			preferredEntity := entity.PreferredEntity{
				StockCode: code,
				StockName: dailyEntity.StockName,
				Time:      d,
				LastClose: dailyEntity.Close,
				LastPrice: dailyEntity.Close,
				Rate:      mathutil.RoundToFloat((dailyEntity.Close-dailyEntity.PreClose)/dailyEntity.PreClose, constants.Precision2),
			}
			_, err = engine.Insert(&preferredEntity)
			if err != nil {
				constants.App.Event.Emit("logs", fmt.Sprintf("[%s]插入数据库失败", code))
				logrus.WithField("股票代码", code).Errorf("插入数据库失败")
				continue
			}
			count++
		}
		constants.App.Event.Emit("logs", fmt.Sprintf("[%s] 评估完成, 优选股票数量: %d", d, count))
		logrus.WithField("日期", d).WithField("优选股票数量", count).Info("评估作完成！")
	}
	time.Sleep(time.Millisecond * 200)
	constants.App.Event.Emit("logs", "评估完成")
	logrus.Info("评估完成！")
	return nil
}

// evaluate 评估股票是否值得购买
// 参数：
//
//	list:[]entity.DailyEntity     当前这只股票的前几天日行情数据
//	configMap:map[string]string   配置参数,key为name值，value 为 content 值
func (ms *MarketService) evaluate(list []entity.DailyEntity, configMap map[string]string) (bool, error) {
	eval, err := evaluator.NewEvaluator(list, configMap)
	if err != nil {
		logrus.Error("创建评估器失败：", err)
		return false, errors.New(fmt.Sprintf("创建评估器失败：%s", err.Error()))
	}
	// ST 股，跳过
	if isST := eval.EvalIsST(); isST {
		return false, nil
	}
	// 科创板，跳过 注意：股票代码前缀：60:上证主板  68:上证科创板  00:深证主板  30: 深圳创业板
	if is68 := eval.EvalIs68(); is68 {
		return false, nil
	}

	// 评估（上个交易日股票）股票价格是否在预定范围内
	if isOk := eval.EvalPrice(); !isOk {
		return false, nil
	}

	// 评估（上个交易日股票）股票换手率是否在预定范围内
	if isOk := eval.EvalTurnoutRate(); !isOk {
		return false, nil
	}

	// 评估（上个交易日股票）总市值是否在预定范围内
	if isOk := eval.EvalAmount(); !isOk {
		return false, nil
	}

	// 评估：MACD，是否处于上升通道
	if isOk := eval.EvalMacd(); !isOk {
		return false, nil
	}
	// 评估：KDJ，是否处于上升通道
	if isOk := eval.EvalKDJ(); !isOk {
		return false, nil
	}

	return true, nil
}

// DeleteHisDailyData 删除历史日行情数据
func (ms *MarketService) DeleteHisDailyData() (int64, error) {
	// 删除3个月前的数据
	affected, err := db.GetMySQL().Where("time < ?", time.Now().AddDate(0, -3, 0).Format(constants.TimeLayout)).Delete(&entity.DailyEntity{})
	if err != nil {
		return 0, err
	} else {
		return affected, nil
	}
}

func (ms *MarketService) GetDailyEntitiesByStockCode(stockCode string) ([]entity.DailyEntity, error) {
	var dailyEntities []entity.DailyEntity
	err := db.GetMySQL().Where("stock_code = ?", stockCode).Desc("time").Find(&dailyEntities)
	if err != nil {
		return nil, err
	}
	return dailyEntities, nil
}

type DayModel struct {
	Label string `json:"label"`
	Value string `json:"value"`
}

func (ms *MarketService) GetDistinctPreferredDays() ([]DayModel, error) {
	var days []string
	err := db.GetMySQL().Distinct("time").Table(entity.PreferredEntity{}).Cols("time").Find(&days)
	if err != nil {
		return nil, err
	}
	var days2 []DayModel
	for _, day := range days {
		days2 = append(days2, DayModel{
			Label: day,
			Value: day,
		})
	}
	sort.Slice(days2, func(i, j int) bool {
		return days2[i].Label > days2[j].Label
	})
	return days2, nil
}
func (ms *MarketService) GetPreferredEntitiesByDay(day string) ([]entity.PreferredEntity, error) {
	logrus.Infof("GetPreferredEntitiesByDay->day:%s", day)
	var preferredEntities []entity.PreferredEntity
	err := db.GetMySQL().Table("t_preferred").Where("time = ?", day).OrderBy("score desc").Find(&preferredEntities)
	if err != nil {
		return nil, err
	}
	return preferredEntities, nil
}
